// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stdint.h>

#include <memory>
#include <vector>

#include "base/bind.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/threading/thread_task_runner_handle.h"
#include "media/base/gmock_callback_support.h"
#include "media/base/mock_filters.h"
#include "media/base/test_helpers.h"
#include "media/renderers/renderer_impl.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::_;
using ::testing::DoAll;
using ::testing::InSequence;
using ::testing::Mock;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::StrictMock;
using ::testing::WithArg;

namespace media {

const int64_t kStartPlayingTimeInMs = 100;

ACTION_P2(SetBufferingState, renderer_client, buffering_state)
{
    (*renderer_client)->OnBufferingStateChange(buffering_state);
}

ACTION_P2(SetError, renderer_client, error)
{
    (*renderer_client)->OnError(error);
}

ACTION(PostCallback)
{
    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, arg0);
}

ACTION(PostQuitWhenIdle)
{
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
}

class RendererImplTest : public ::testing::Test {
public:
    // Used for setting expectations on pipeline callbacks. Using a StrictMock
    // also lets us test for missing callbacks.
    class CallbackHelper : public MockRendererClient {
    public:
        CallbackHelper() { }
        virtual ~CallbackHelper() { }

        // Completion callbacks.
        MOCK_METHOD1(OnInitialize, void(PipelineStatus));
        MOCK_METHOD0(OnFlushed, void());
        MOCK_METHOD1(OnCdmAttached, void(bool));
        MOCK_METHOD1(OnDurationChange, void(base::TimeDelta duration));

    private:
        DISALLOW_COPY_AND_ASSIGN(CallbackHelper);
    };

    RendererImplTest()
        : demuxer_(new StrictMock<MockDemuxer>())
        , video_renderer_(new StrictMock<MockVideoRenderer>())
        , audio_renderer_(new StrictMock<MockAudioRenderer>())
        , renderer_impl_(
              new RendererImpl(message_loop_.task_runner(),
                  std::unique_ptr<AudioRenderer>(audio_renderer_),
                  std::unique_ptr<VideoRenderer>(video_renderer_)))
        , cdm_context_(new StrictMock<MockCdmContext>())
        , video_renderer_client_(nullptr)
        , audio_renderer_client_(nullptr)
        , initialization_status_(PIPELINE_OK)
    {
        // CreateAudioStream() and CreateVideoStream() overrides expectations for
        // expected non-NULL streams.
        DemuxerStream* null_pointer = NULL;
        EXPECT_CALL(*demuxer_, GetStream(_))
            .WillRepeatedly(Return(null_pointer));
    }

    virtual ~RendererImplTest() { Destroy(); }

protected:
    void Destroy()
    {
        renderer_impl_.reset();
        base::RunLoop().RunUntilIdle();
    }

    std::unique_ptr<StrictMock<MockDemuxerStream>> CreateStream(
        DemuxerStream::Type type)
    {
        std::unique_ptr<StrictMock<MockDemuxerStream>> stream(
            new StrictMock<MockDemuxerStream>(type));
        EXPECT_CALL(*stream, SetStreamStatusChangeCB(_))
            .Times(testing::AnyNumber());
        return stream;
    }

    // Sets up expectations to allow the audio renderer to initialize.
    void SetAudioRendererInitializeExpectations(PipelineStatus status)
    {
        EXPECT_CALL(*audio_renderer_, Initialize(audio_stream_.get(), _, _, _))
            .WillOnce(
                DoAll(SaveArg<2>(&audio_renderer_client_), RunCallback<3>(status)));
    }

    // Sets up expectations to allow the video renderer to initialize.
    void SetVideoRendererInitializeExpectations(PipelineStatus status)
    {
        EXPECT_CALL(*video_renderer_, Initialize(video_stream_.get(), _, _, _, _))
            .WillOnce(
                DoAll(SaveArg<2>(&video_renderer_client_), RunCallback<4>(status)));
    }

    void InitializeAndExpect(PipelineStatus start_status)
    {
        EXPECT_CALL(callbacks_, OnInitialize(start_status))
            .WillOnce(SaveArg<0>(&initialization_status_));
        EXPECT_CALL(callbacks_, OnWaitingForDecryptionKey()).Times(0);

        if (start_status == PIPELINE_OK && audio_stream_) {
            EXPECT_CALL(*audio_renderer_, GetTimeSource())
                .WillOnce(Return(&time_source_));
        } else {
            renderer_impl_->set_time_source_for_testing(&time_source_);
        }

        renderer_impl_->Initialize(demuxer_.get(), &callbacks_,
            base::Bind(&CallbackHelper::OnInitialize,
                base::Unretained(&callbacks_)));
        base::RunLoop().RunUntilIdle();
    }

    void CreateAudioStream()
    {
        audio_stream_ = CreateStream(DemuxerStream::AUDIO);
        EXPECT_CALL(*demuxer_, GetStream(DemuxerStream::AUDIO))
            .WillRepeatedly(Return(audio_stream_.get()));
    }

    void CreateVideoStream(bool is_encrypted = false)
    {
        video_stream_ = CreateStream(DemuxerStream::VIDEO);
        video_stream_->set_video_decoder_config(
            is_encrypted ? TestVideoConfig::NormalEncrypted()
                         : TestVideoConfig::Normal());
        EXPECT_CALL(*demuxer_, GetStream(DemuxerStream::VIDEO))
            .WillRepeatedly(Return(video_stream_.get()));
    }

    void CreateEncryptedVideoStream() { CreateVideoStream(true); }

    void CreateAudioAndVideoStream()
    {
        CreateAudioStream();
        CreateVideoStream();
    }

    void InitializeWithAudio()
    {
        CreateAudioStream();
        SetAudioRendererInitializeExpectations(PIPELINE_OK);
        // There is a potential race between HTMLMediaElement/WMPI shutdown and
        // renderers being initialized which might result in DemuxerStreamProvider
        // GetStream suddenly returning NULL (see crbug.com/668604). So we are going
        // to check here that GetStream will be invoked exactly 3 times during
        // RendererImpl initialization to help catch potential issues. Currently the
        // GetStream is invoked once directly from RendererImpl::Initialize, once
        // indirectly from RendererImpl::Initialize via HasEncryptedStream and once
        // from RendererImpl::InitializeAudioRenderer.
        EXPECT_CALL(*demuxer_, GetStream(DemuxerStream::AUDIO))
            .Times(2)
            .WillRepeatedly(Return(audio_stream_.get()));
        InitializeAndExpect(PIPELINE_OK);
    }

    void InitializeWithVideo()
    {
        CreateVideoStream();
        SetVideoRendererInitializeExpectations(PIPELINE_OK);
        // There is a potential race between HTMLMediaElement/WMPI shutdown and
        // renderers being initialized which might result in DemuxerStreamProvider
        // GetStream suddenly returning NULL (see crbug.com/668604). So we are going
        // to check here that GetStream will be invoked exactly 3 times during
        // RendererImpl initialization to help catch potential issues. Currently the
        // GetStream is invoked once directly from RendererImpl::Initialize, once
        // indirectly from RendererImpl::Initialize via HasEncryptedStream and once
        // from RendererImpl::InitializeVideoRenderer.
        EXPECT_CALL(*demuxer_, GetStream(DemuxerStream::VIDEO))
            .Times(2)
            .WillRepeatedly(Return(video_stream_.get()));
        InitializeAndExpect(PIPELINE_OK);
    }

    void InitializeWithAudioAndVideo()
    {
        CreateAudioAndVideoStream();
        SetAudioRendererInitializeExpectations(PIPELINE_OK);
        SetVideoRendererInitializeExpectations(PIPELINE_OK);
        InitializeAndExpect(PIPELINE_OK);
    }

    void Play()
    {
        DCHECK(audio_stream_ || video_stream_);
        EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH));

        base::TimeDelta start_time(
            base::TimeDelta::FromMilliseconds(kStartPlayingTimeInMs));
        EXPECT_CALL(time_source_, SetMediaTime(start_time));
        EXPECT_CALL(time_source_, StartTicking());

        if (audio_stream_) {
            EXPECT_CALL(*audio_renderer_, StartPlaying())
                .WillOnce(SetBufferingState(&audio_renderer_client_,
                    BUFFERING_HAVE_ENOUGH));
        }

        if (video_stream_) {
            EXPECT_CALL(*video_renderer_, StartPlayingFrom(start_time))
                .WillOnce(SetBufferingState(&video_renderer_client_,
                    BUFFERING_HAVE_ENOUGH));
        }

        renderer_impl_->StartPlayingFrom(start_time);
        base::RunLoop().RunUntilIdle();
    }

    void Flush(bool underflowed)
    {
        if (!underflowed)
            EXPECT_CALL(time_source_, StopTicking());

        if (audio_stream_) {
            EXPECT_CALL(*audio_renderer_, Flush(_))
                .WillOnce(DoAll(SetBufferingState(&audio_renderer_client_,
                                    BUFFERING_HAVE_NOTHING),
                    RunClosure<0>()));
        }

        if (video_stream_) {
            EXPECT_CALL(*video_renderer_, Flush(_))
                .WillOnce(DoAll(SetBufferingState(&video_renderer_client_,
                                    BUFFERING_HAVE_NOTHING),
                    RunClosure<0>()));
        }

        EXPECT_CALL(callbacks_, OnFlushed());

        renderer_impl_->Flush(
            base::Bind(&CallbackHelper::OnFlushed, base::Unretained(&callbacks_)));
        base::RunLoop().RunUntilIdle();
    }

    void SetPlaybackRate(double playback_rate)
    {
        EXPECT_CALL(time_source_, SetPlaybackRate(playback_rate));
        renderer_impl_->SetPlaybackRate(playback_rate);
        base::RunLoop().RunUntilIdle();
    }

    int64_t GetMediaTimeMs()
    {
        return renderer_impl_->GetMediaTime().InMilliseconds();
    }

    bool IsMediaTimeAdvancing(double playback_rate)
    {
        int64_t start_time_ms = GetMediaTimeMs();
        const int64_t time_to_advance_ms = 100;

        test_tick_clock_.Advance(
            base::TimeDelta::FromMilliseconds(time_to_advance_ms));

        if (GetMediaTimeMs() == start_time_ms + time_to_advance_ms * playback_rate)
            return true;

        DCHECK_EQ(start_time_ms, GetMediaTimeMs());
        return false;
    }

    bool IsMediaTimeAdvancing()
    {
        return IsMediaTimeAdvancing(1.0);
    }

    void SetCdmAndExpect(bool expected_result)
    {
        EXPECT_CALL(callbacks_, OnCdmAttached(expected_result));
        renderer_impl_->SetCdm(cdm_context_.get(),
            base::Bind(&CallbackHelper::OnCdmAttached,
                base::Unretained(&callbacks_)));
        base::RunLoop().RunUntilIdle();
    }

    // Fixture members.
    base::MessageLoop message_loop_;
    StrictMock<CallbackHelper> callbacks_;
    base::SimpleTestTickClock test_tick_clock_;

    std::unique_ptr<StrictMock<MockDemuxer>> demuxer_;
    StrictMock<MockVideoRenderer>* video_renderer_;
    StrictMock<MockAudioRenderer>* audio_renderer_;
    std::unique_ptr<RendererImpl> renderer_impl_;
    std::unique_ptr<StrictMock<MockCdmContext>> cdm_context_;

    StrictMock<MockTimeSource> time_source_;
    std::unique_ptr<StrictMock<MockDemuxerStream>> audio_stream_;
    std::unique_ptr<StrictMock<MockDemuxerStream>> video_stream_;
    RendererClient* video_renderer_client_;
    RendererClient* audio_renderer_client_;
    VideoDecoderConfig video_decoder_config_;
    PipelineStatus initialization_status_;

private:
    DISALLOW_COPY_AND_ASSIGN(RendererImplTest);
};

TEST_F(RendererImplTest, Destroy_BeforeInitialize)
{
    Destroy();
}

TEST_F(RendererImplTest, Destroy_PendingInitialize)
{
    CreateAudioAndVideoStream();

    SetAudioRendererInitializeExpectations(PIPELINE_OK);
    // Not returning the video initialization callback.
    EXPECT_CALL(*video_renderer_, Initialize(video_stream_.get(), _, _, _, _));

    InitializeAndExpect(PIPELINE_ERROR_ABORT);
    EXPECT_EQ(PIPELINE_OK, initialization_status_);

    Destroy();
}

TEST_F(RendererImplTest, Destroy_PendingInitializeWithoutCdm)
{
    CreateAudioStream();
    CreateEncryptedVideoStream();

    // Audio is clear and video is encrypted. Initialization will not start
    // because no CDM is set. So neither AudioRenderer::Initialize() nor
    // VideoRenderer::Initialize() should not be called. The InitCB will be
    // aborted when |renderer_impl_| is destructed.
    InitializeAndExpect(PIPELINE_ERROR_ABORT);
    EXPECT_EQ(PIPELINE_OK, initialization_status_);

    Destroy();
}

TEST_F(RendererImplTest, Destroy_PendingInitializeAfterSetCdm)
{
    CreateAudioStream();
    CreateEncryptedVideoStream();

    // Audio is clear and video is encrypted. Initialization will not start
    // because no CDM is set.
    InitializeAndExpect(PIPELINE_ERROR_ABORT);
    EXPECT_EQ(PIPELINE_OK, initialization_status_);

    SetAudioRendererInitializeExpectations(PIPELINE_OK);
    // Not returning the video initialization callback. So initialization will
    // be pending.
    EXPECT_CALL(*video_renderer_, Initialize(video_stream_.get(), _, _, _, _));

    // SetCdm() will trigger the initialization to start. But it will not complete
    // because the |video_renderer_| is not returning the initialization callback.
    SetCdmAndExpect(false);
    EXPECT_EQ(PIPELINE_OK, initialization_status_);

    Destroy();
}

TEST_F(RendererImplTest, InitializeWithAudio)
{
    InitializeWithAudio();
}

TEST_F(RendererImplTest, InitializeWithVideo)
{
    InitializeWithVideo();
}

TEST_F(RendererImplTest, InitializeWithAudioVideo)
{
    InitializeWithAudioAndVideo();
}

TEST_F(RendererImplTest, InitializeWithAudio_Failed)
{
    CreateAudioStream();
    SetAudioRendererInitializeExpectations(PIPELINE_ERROR_INITIALIZATION_FAILED);
    InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED);
}

TEST_F(RendererImplTest, InitializeWithVideo_Failed)
{
    CreateVideoStream();
    SetVideoRendererInitializeExpectations(PIPELINE_ERROR_INITIALIZATION_FAILED);
    InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED);
}

TEST_F(RendererImplTest, InitializeWithAudioVideo_AudioRendererFailed)
{
    CreateAudioAndVideoStream();
    SetAudioRendererInitializeExpectations(PIPELINE_ERROR_INITIALIZATION_FAILED);
    // VideoRenderer::Initialize() should not be called.
    InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED);
}

TEST_F(RendererImplTest, InitializeWithAudioVideo_VideoRendererFailed)
{
    CreateAudioAndVideoStream();
    SetAudioRendererInitializeExpectations(PIPELINE_OK);
    SetVideoRendererInitializeExpectations(PIPELINE_ERROR_INITIALIZATION_FAILED);
    InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED);
}

TEST_F(RendererImplTest, SetCdmBeforeInitialize)
{
    // CDM will be successfully attached immediately if set before RendererImpl
    // initialization, regardless of the later initialization result.
    SetCdmAndExpect(true);
}

TEST_F(RendererImplTest, SetCdmAfterInitialize_ClearStream)
{
    InitializeWithAudioAndVideo();
    EXPECT_EQ(PIPELINE_OK, initialization_status_);

    // CDM will be successfully attached immediately since initialization is
    // completed.
    SetCdmAndExpect(true);
}

TEST_F(RendererImplTest, SetCdmAfterInitialize_EncryptedStream_Success)
{
    CreateAudioStream();
    CreateEncryptedVideoStream();

    SetAudioRendererInitializeExpectations(PIPELINE_OK);
    SetVideoRendererInitializeExpectations(PIPELINE_OK);
    InitializeAndExpect(PIPELINE_OK);
    // Initialization is pending until CDM is set.
    EXPECT_EQ(PIPELINE_OK, initialization_status_);

    SetCdmAndExpect(true);
    EXPECT_EQ(PIPELINE_OK, initialization_status_);
}

TEST_F(RendererImplTest, SetCdmAfterInitialize_EncryptedStream_Failure)
{
    CreateAudioStream();
    CreateEncryptedVideoStream();

    SetAudioRendererInitializeExpectations(PIPELINE_OK);
    SetVideoRendererInitializeExpectations(PIPELINE_ERROR_INITIALIZATION_FAILED);
    InitializeAndExpect(PIPELINE_ERROR_INITIALIZATION_FAILED);
    // Initialization is pending until CDM is set.
    EXPECT_EQ(PIPELINE_OK, initialization_status_);

    SetCdmAndExpect(false);
    EXPECT_EQ(PIPELINE_ERROR_INITIALIZATION_FAILED, initialization_status_);
}

TEST_F(RendererImplTest, SetCdmMultipleTimes)
{
    SetCdmAndExpect(true);
    SetCdmAndExpect(false); // Do not support switching CDM.
}

TEST_F(RendererImplTest, StartPlayingFrom)
{
    InitializeWithAudioAndVideo();
    Play();
}

TEST_F(RendererImplTest, StartPlayingFromWithPlaybackRate)
{
    InitializeWithAudioAndVideo();

    // Play with a zero playback rate shouldn't start time.
    Play();
    Mock::VerifyAndClearExpectations(video_renderer_);

    // Positive playback rate when ticking should start time.
    EXPECT_CALL(*video_renderer_, OnTimeProgressing());
    SetPlaybackRate(1.0);
    Mock::VerifyAndClearExpectations(video_renderer_);

    // Double notifications shouldn't be sent.
    SetPlaybackRate(1.0);
    Mock::VerifyAndClearExpectations(video_renderer_);

    // Zero playback rate should stop time.
    EXPECT_CALL(*video_renderer_, OnTimeStopped());
    SetPlaybackRate(0.0);
    Mock::VerifyAndClearExpectations(video_renderer_);

    // Double notifications shouldn't be sent.
    SetPlaybackRate(0.0);
    Mock::VerifyAndClearExpectations(video_renderer_);

    // Starting playback and flushing should cause time to stop.
    EXPECT_CALL(*video_renderer_, OnTimeProgressing());
    EXPECT_CALL(*video_renderer_, OnTimeStopped());
    SetPlaybackRate(1.0);
    Flush(false);

    // A positive playback rate when playback isn't started should do nothing.
    SetPlaybackRate(1.0);
}

TEST_F(RendererImplTest, FlushAfterInitialization)
{
    InitializeWithAudioAndVideo();
    Flush(true);
}

TEST_F(RendererImplTest, FlushAfterPlay)
{
    InitializeWithAudioAndVideo();
    Play();
    Flush(false);
}

TEST_F(RendererImplTest, FlushAfterUnderflow)
{
    InitializeWithAudioAndVideo();
    Play();

    // Simulate underflow.
    EXPECT_CALL(time_source_, StopTicking());
    EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING));
    audio_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_NOTHING);

    // Flush while underflowed. We shouldn't call StopTicking() again.
    Flush(true);
}

TEST_F(RendererImplTest, SetPlaybackRate)
{
    InitializeWithAudioAndVideo();
    SetPlaybackRate(1.0);
    SetPlaybackRate(2.0);
}

TEST_F(RendererImplTest, SetVolume)
{
    InitializeWithAudioAndVideo();
    EXPECT_CALL(*audio_renderer_, SetVolume(2.0f));
    renderer_impl_->SetVolume(2.0f);
}

TEST_F(RendererImplTest, AudioStreamEnded)
{
    InitializeWithAudio();
    Play();

    EXPECT_CALL(time_source_, StopTicking());
    EXPECT_CALL(callbacks_, OnEnded());

    audio_renderer_client_->OnEnded();
    base::RunLoop().RunUntilIdle();
}

TEST_F(RendererImplTest, VideoStreamEnded)
{
    InitializeWithVideo();
    Play();

    EXPECT_CALL(time_source_, StopTicking());
    EXPECT_CALL(callbacks_, OnEnded());
    EXPECT_CALL(*video_renderer_, OnTimeStopped());

    video_renderer_client_->OnEnded();
    base::RunLoop().RunUntilIdle();
}

TEST_F(RendererImplTest, AudioVideoStreamsEnded)
{
    InitializeWithAudioAndVideo();
    Play();

    // OnEnded() is called only when all streams have finished.
    audio_renderer_client_->OnEnded();
    base::RunLoop().RunUntilIdle();

    EXPECT_CALL(time_source_, StopTicking());
    EXPECT_CALL(callbacks_, OnEnded());
    EXPECT_CALL(*video_renderer_, OnTimeStopped());

    video_renderer_client_->OnEnded();
    base::RunLoop().RunUntilIdle();
}

TEST_F(RendererImplTest, ErrorAfterInitialize)
{
    InitializeWithAudio();
    EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_DECODE));
    audio_renderer_client_->OnError(PIPELINE_ERROR_DECODE);
    base::RunLoop().RunUntilIdle();
}

TEST_F(RendererImplTest, ErrorDuringPlaying)
{
    InitializeWithAudio();
    Play();

    EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_DECODE));
    audio_renderer_client_->OnError(PIPELINE_ERROR_DECODE);
    base::RunLoop().RunUntilIdle();
}

TEST_F(RendererImplTest, ErrorDuringFlush)
{
    InitializeWithAudio();
    Play();

    InSequence s;
    EXPECT_CALL(time_source_, StopTicking());
    EXPECT_CALL(*audio_renderer_, Flush(_))
        .WillOnce(DoAll(SetError(&audio_renderer_client_, PIPELINE_ERROR_DECODE),
            RunClosure<0>()));
    EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_DECODE));
    EXPECT_CALL(callbacks_, OnFlushed());
    renderer_impl_->Flush(
        base::Bind(&CallbackHelper::OnFlushed, base::Unretained(&callbacks_)));
    base::RunLoop().RunUntilIdle();
}

TEST_F(RendererImplTest, ErrorAfterFlush)
{
    InitializeWithAudio();
    Play();
    Flush(false);

    EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_DECODE));
    audio_renderer_client_->OnError(PIPELINE_ERROR_DECODE);
    base::RunLoop().RunUntilIdle();
}

TEST_F(RendererImplTest, ErrorDuringInitialize)
{
    CreateAudioAndVideoStream();
    SetAudioRendererInitializeExpectations(PIPELINE_OK);

    // Force an audio error to occur during video renderer initialization.
    EXPECT_CALL(*video_renderer_, Initialize(video_stream_.get(), _, _, _, _))
        .WillOnce(DoAll(SetError(&audio_renderer_client_, PIPELINE_ERROR_DECODE),
            SaveArg<2>(&video_renderer_client_),
            RunCallback<4>(PIPELINE_OK)));

    InitializeAndExpect(PIPELINE_ERROR_DECODE);
}

TEST_F(RendererImplTest, AudioUnderflow)
{
    InitializeWithAudio();
    Play();

    // Underflow should occur immediately with a single audio track.
    EXPECT_CALL(time_source_, StopTicking());
    EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING));
    audio_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_NOTHING);
}

TEST_F(RendererImplTest, AudioUnderflowWithVideo)
{
    InitializeWithAudioAndVideo();
    Play();

    // Underflow should be immediate when both audio and video are present and
    // audio underflows.
    EXPECT_CALL(time_source_, StopTicking());
    EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING));
    audio_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_NOTHING);
}

TEST_F(RendererImplTest, VideoUnderflow)
{
    InitializeWithVideo();
    Play();

    // Underflow should occur immediately with a single video track.
    EXPECT_CALL(time_source_, StopTicking());
    EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING));
    video_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_NOTHING);
}

TEST_F(RendererImplTest, VideoUnderflowWithAudio)
{
    InitializeWithAudioAndVideo();
    Play();

    // Set a zero threshold such that the underflow will be executed on the next
    // run of the message loop.
    renderer_impl_->set_video_underflow_threshold_for_testing(base::TimeDelta());

    // Underflow should be delayed when both audio and video are present and video
    // underflows.
    EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING));
    video_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_NOTHING);
    Mock::VerifyAndClearExpectations(&time_source_);

    EXPECT_CALL(time_source_, StopTicking());
    base::RunLoop().RunUntilIdle();
}

TEST_F(RendererImplTest, VideoUnderflowWithAudioVideoRecovers)
{
    InitializeWithAudioAndVideo();
    Play();

    // Set a zero threshold such that the underflow will be executed on the next
    // run of the message loop.
    renderer_impl_->set_video_underflow_threshold_for_testing(base::TimeDelta());

    // Underflow should be delayed when both audio and video are present and video
    // underflows.
    EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING))
        .Times(0);
    video_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_NOTHING);
    Mock::VerifyAndClearExpectations(&time_source_);

    // If video recovers, the underflow should never occur.
    video_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_ENOUGH);
    base::RunLoop().RunUntilIdle();
}

TEST_F(RendererImplTest, VideoAndAudioUnderflow)
{
    InitializeWithAudioAndVideo();
    Play();

    // Set a zero threshold such that the underflow will be executed on the next
    // run of the message loop.
    renderer_impl_->set_video_underflow_threshold_for_testing(base::TimeDelta());

    // Underflow should be delayed when both audio and video are present and video
    // underflows.
    EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING))
        .Times(0);
    video_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_NOTHING);
    Mock::VerifyAndClearExpectations(&time_source_);

    EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING));
    EXPECT_CALL(time_source_, StopTicking());
    audio_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_NOTHING);

    // Nothing else should primed on the message loop.
    base::RunLoop().RunUntilIdle();
}

TEST_F(RendererImplTest, VideoUnderflowWithAudioFlush)
{
    InitializeWithAudioAndVideo();
    Play();

    // Set a massive threshold such that it shouldn't fire within this test.
    renderer_impl_->set_video_underflow_threshold_for_testing(
        base::TimeDelta::FromSeconds(100));

    // Simulate the cases where audio underflows and then video underflows.
    EXPECT_CALL(time_source_, StopTicking());
    EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING));
    audio_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_NOTHING);
    video_renderer_client_->OnBufferingStateChange(BUFFERING_HAVE_NOTHING);
    Mock::VerifyAndClearExpectations(&time_source_);

    // Flush the audio and video renderers, both think they're in an underflow
    // state, but if the video renderer underflow was deferred, RendererImpl would
    // think it still has enough data.
    EXPECT_CALL(*audio_renderer_, Flush(_)).WillOnce(RunClosure<0>());
    EXPECT_CALL(*video_renderer_, Flush(_)).WillOnce(RunClosure<0>());
    EXPECT_CALL(callbacks_, OnFlushed());
    renderer_impl_->Flush(
        base::Bind(&CallbackHelper::OnFlushed, base::Unretained(&callbacks_)));
    base::RunLoop().RunUntilIdle();

    // Start playback after the flush, but never return BUFFERING_HAVE_ENOUGH from
    // the video renderer (which simulates spool up time for the video renderer).
    const base::TimeDelta kStartTime;
    EXPECT_CALL(time_source_, SetMediaTime(kStartTime));
    EXPECT_CALL(*audio_renderer_, StartPlaying())
        .WillOnce(
            SetBufferingState(&audio_renderer_client_, BUFFERING_HAVE_ENOUGH));
    EXPECT_CALL(*video_renderer_, StartPlayingFrom(kStartTime));
    renderer_impl_->StartPlayingFrom(kStartTime);

    // Nothing else should primed on the message loop.
    base::RunLoop().RunUntilIdle();
}

TEST_F(RendererImplTest, StreamStatusNotificationHandling)
{
    CreateAudioAndVideoStream();

    DemuxerStream::StreamStatusChangeCB audio_stream_status_change_cb;
    DemuxerStream::StreamStatusChangeCB video_stream_status_change_cb;
    EXPECT_CALL(*audio_stream_, SetStreamStatusChangeCB(_))
        .WillOnce(SaveArg<0>(&audio_stream_status_change_cb));
    EXPECT_CALL(*video_stream_, SetStreamStatusChangeCB(_))
        .WillOnce(SaveArg<0>(&video_stream_status_change_cb));
    SetAudioRendererInitializeExpectations(PIPELINE_OK);
    SetVideoRendererInitializeExpectations(PIPELINE_OK);
    InitializeAndExpect(PIPELINE_OK);
    Play();

    // Verify that DemuxerStream status changes cause the corresponding
    // audio/video renderer to be flushed and restarted.
    EXPECT_CALL(time_source_, StopTicking());
    EXPECT_CALL(*audio_renderer_, Flush(_)).WillOnce(RunClosure<0>());
    EXPECT_CALL(*audio_renderer_, StartPlaying())
        .Times(1)
        .WillOnce(
            SetBufferingState(&audio_renderer_client_, BUFFERING_HAVE_ENOUGH));
    audio_stream_status_change_cb.Run(false, base::TimeDelta());

    EXPECT_CALL(*video_renderer_, Flush(_)).WillOnce(RunClosure<0>());
    EXPECT_CALL(*video_renderer_, StartPlayingFrom(_))
        .Times(1)
        .WillOnce(DoAll(
            SetBufferingState(&video_renderer_client_, BUFFERING_HAVE_ENOUGH),
            PostQuitWhenIdle()));

    video_stream_status_change_cb.Run(false, base::TimeDelta());
    base::RunLoop().Run();
}

// Stream status changes are handled asynchronously by the renderer and may take
// some time to process. This test verifies that all status changes are
// processed correctly by the renderer even if status changes of the stream
// happen much faster than the renderer can process them. In that case the
// renderer may postpone processing status changes, but still must process all
// of them eventually.
TEST_F(RendererImplTest, PostponedStreamStatusNotificationHandling)
{
    CreateAudioAndVideoStream();

    DemuxerStream::StreamStatusChangeCB audio_stream_status_change_cb;
    DemuxerStream::StreamStatusChangeCB video_stream_status_change_cb;
    EXPECT_CALL(*audio_stream_, SetStreamStatusChangeCB(_))
        .WillOnce(SaveArg<0>(&audio_stream_status_change_cb));
    EXPECT_CALL(*video_stream_, SetStreamStatusChangeCB(_))
        .WillOnce(SaveArg<0>(&video_stream_status_change_cb));
    SetAudioRendererInitializeExpectations(PIPELINE_OK);
    SetVideoRendererInitializeExpectations(PIPELINE_OK);
    InitializeAndExpect(PIPELINE_OK);
    Play();

    EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH))
        .Times(2);

    EXPECT_CALL(time_source_, StopTicking()).Times(2);
    EXPECT_CALL(time_source_, StartTicking()).Times(2);
    EXPECT_CALL(*audio_renderer_, Flush(_))
        .Times(2)
        .WillRepeatedly(DoAll(
            SetBufferingState(&audio_renderer_client_, BUFFERING_HAVE_NOTHING),
            WithArg<0>(PostCallback())));
    EXPECT_CALL(*audio_renderer_, StartPlaying())
        .Times(2)
        .WillOnce(
            SetBufferingState(&audio_renderer_client_, BUFFERING_HAVE_ENOUGH))
        .WillOnce(DoAll(
            SetBufferingState(&audio_renderer_client_, BUFFERING_HAVE_ENOUGH),
            PostQuitWhenIdle()));
    // The first stream status change will be processed immediately. Each status
    // change processing involves Flush + StartPlaying when the Flush is done. The
    // Flush operation is async in this case, so the second status change will be
    // postponed by renderer until after processing the first one is finished. But
    // we must still get two pairs of Flush/StartPlaying calls eventually.
    audio_stream_status_change_cb.Run(false, base::TimeDelta());
    audio_stream_status_change_cb.Run(true, base::TimeDelta());
    base::RunLoop().Run();

    EXPECT_CALL(*video_renderer_, Flush(_))
        .Times(2)
        .WillRepeatedly(DoAll(
            SetBufferingState(&video_renderer_client_, BUFFERING_HAVE_NOTHING),
            WithArg<0>(PostCallback())));
    EXPECT_CALL(*video_renderer_, StartPlayingFrom(base::TimeDelta()))
        .Times(2)
        .WillOnce(
            SetBufferingState(&video_renderer_client_, BUFFERING_HAVE_ENOUGH))
        .WillOnce(DoAll(
            SetBufferingState(&video_renderer_client_, BUFFERING_HAVE_ENOUGH),
            PostQuitWhenIdle()));
    // The first stream status change will be processed immediately. Each status
    // change processing involves Flush + StartPlaying when the Flush is done. The
    // Flush operation is async in this case, so the second status change will be
    // postponed by renderer until after processing the first one is finished. But
    // we must still get two pairs of Flush/StartPlaying calls eventually.
    video_stream_status_change_cb.Run(false, base::TimeDelta());
    video_stream_status_change_cb.Run(true, base::TimeDelta());
    base::RunLoop().Run();
}

} // namespace media
